/**
 * 
 */
package gpl.scotlandyard.fugitivepathfinder;

import gpl.scotlandyard.beans.MarkedNode;
import gpl.scotlandyard.beans.basics.Link;
import gpl.scotlandyard.beans.basics.Node;
import gpl.scotlandyard.beans.basics.Player;
import gpl.scotlandyard.beans.basics.Ticket;
import gpl.scotlandyard.services.FreePlayerManager;
import gpl.scotlandyard.services.basics.BoardManagerBuilder;
import gpl.scotlandyard.services.utils.MapLoader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

/** @author norbert The main idea is to view trends of algorithm to help fugitive to find secured path. 1- how many turns
 * can be projected with 4/5 detectives ? 2- proposed paths are not only "deadlock paths" ? 3- can be set an percent of
 * acceptable risk to have improved paths ? */
public class PathFinder {

  private static final Logger LOG = Logger.getLogger(PathFinder.class);

  private static final List<Node> MAP = new ArrayList<Node>();
  static {
    BufferedReader bufr;
    try {
      bufr = new BufferedReader(new FileReader(new File(MainRecursive.MAP_FILE)));
      MAP.addAll(MapLoader.loadMap(bufr));
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      System.exit(-1);
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(-1);
    }
  }

  private MyTracker fugitiveTracker;
  private List<MyTracker> trackers;
  private FreePlayerManager playerMgr = new FreePlayerManager();
  private HashMap<Player, List<Node>> nextDetectives = new HashMap<Player, List<Node>>();
  private List<Node> nextFugitive;
  private int currentTurn = 0;

  private final int NB_DETECTIVES;
  private final int NB_TURNS;

  /** @param nbDetectives
   * @param nbTurns */
  public PathFinder(int nbDetectives, int nbTurns) {
    NB_DETECTIVES = nbDetectives;
    NB_TURNS = nbTurns;
    trackers = new ArrayList<MyTracker>(NB_DETECTIVES);
  }

  /** @param fw
   * @throws IOException */
  public void run(FileWriter fw) throws IOException {
    // have random game
    BoardManagerBuilder builder = new BoardManagerBuilder();
    Player fug = new Player("Fugitif");
    fugitiveTracker = new MyTracker(fug);
    fug.setFugitive(true);
    builder.addFugitive(fug);
    for (int i = 1; i <= NB_DETECTIVES; i++) {
      Player det = new Player("detective" + i);
      trackers.add(new MyTracker(det));
      builder.addDetective(det);
    }
    builder.setMap(MAP);
    builder.setPlayerManager(playerMgr);
    builder.getRandomBoardManager();

    nextFugitive = Collections.singletonList(fug.getNode());
    LOG.error("fugitive : " + fug.getNode());
    for (MyTracker mt : trackers) {
      nextDetectives.put(mt.getPlayer(), Collections.singletonList(mt.getPlayer().getNode()));
      LOG.error(mt.getPlayer().getName() + " " + mt.getPlayer().getNode());
    }
    String out = "";
    int result = 0;
    for (currentTurn = 1; currentTurn <= NB_TURNS; currentTurn++) {
      if (turnX(currentTurn) == 0) {
        out = "max turns safe : " + currentTurn;
        result = currentTurn;
        break;
      }
    }
    LOG.error(out);
    fw.write(result + "");
    fw.write("\r\n");
  }

  /** @param x
   * @return number of possible moves for fugitive */
  private int turnX(int x) {
    LOG.info("---- TURN " + x + " ----");
    fugitiveTracker.setPossiblePositions(nextFugitive);
    for (MyTracker mt : trackers) {
      mt.setPossiblePositions(nextDetectives.get(mt.getPlayer()));
    }
    caclDetectiveNextPositions();
    List<Link> nextLinks = calFugitiveNextSafePosition();
    LOG.error("#" + x + " fugitive safe next moves : " + nextLinks);
    return nextLinks.size();
  }

  private List<Link> calFugitiveNextSafePosition() {
    Player playerClone = fugitiveTracker.getPlayer().clone();
    List<Link> fugMoves = new LinkedList<Link>();
    for (MarkedNode possibleNode : fugitiveTracker.getPossiblePositions()) {
      playerClone.setNode(possibleNode.getNode());
      fugMoves.addAll(playerMgr.getMoves(playerClone));
    }
    // keep only destination nodes
    List<Node> fugNodes = new ArrayList<Node>();
    for (Link link : fugMoves) {
      if (!fugNodes.contains(link.getToNode())) {
        fugNodes.add(link.getToNode());
      }
    }
    LOG.error("fugitive potential next nodes [" + fugNodes.size() + "] : " + fugNodes);

    // remove impossible nodes cause already busied
    HashSet<Node> toDel = new HashSet<Node>();
    for (MyTracker tracker : trackers) {
      for (Node nd : fugNodes) {
        if (nd.equals(tracker.getPlayer().getNode())) {
          toDel.add(nd);
        }
      }
    }
    LOG.error("current busied nodes to del [" + toDel.size() + "] : " + toDel);
    if (currentTurn == 1 && toDel.size() > NB_DETECTIVES) {
      throw new IllegalStateException("more busied case than detectives");
    }
    fugNodes.removeAll(toDel);

    // remove next potential busied nodes
    toDel.clear();
    for (List<Node> list : nextDetectives.values()) {
      for (Node nd : list) {
        if (fugNodes.contains(nd)) {
          toDel.add(nd);
        }
      }
    }
    LOG.error("next unsafe nodes to del [" + toDel.size() + "]: " + toDel);
    fugNodes.removeAll(toDel);

    // keep safe moves
    LOG.error("fugitive safe next nodes [" + fugNodes.size() + "] : " + fugNodes);
    nextFugitive = fugNodes;
    List<Link> safe = new ArrayList<Link>();
    for (Link link : fugMoves) {
      if (fugNodes.contains(link.getToNode())) {
        safe.add(link);
      }
    }
    return safe;
  }

  private void caclDetectiveNextPositions() {
    nextDetectives = new HashMap<Player, List<Node>>();
    for (MyTracker tracker : trackers) {
      List<Node> nexts = new ArrayList<Node>();
      Player playerClone = tracker.getPlayer().clone();
      for (MarkedNode currNode : tracker.getPossiblePositions()) {
        playerMgr.move(playerClone, new Link(currNode.getNode(), currNode.getNode(), Ticket.HIDE));
        for (Link nextMoves : playerMgr.getMoves(playerClone)) {
          if (!nexts.contains(nextMoves.getToNode())) {
            nexts.add(nextMoves.getToNode());
          }
        }
      }
      nextDetectives.put(tracker.getPlayer(), nexts);
    }
  }

}
